package com.sd.repay.utils;

import com.sd.repay.common.Constants;
import com.sd.repay.pojo.PlanConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @author sd
 * @Description: 快捷还款算法
 * @date 2018年06月29日
 */
public class CalRepaymentQuickPlan {

    private static Logger log = LoggerFactory.getLogger(CalRepaymentQuickPlan.class);

    public static int quickRepayMinAmount;//最小还款金额
    public static int quickRepayMaxAmount;//最大还款金额
    public static int quickDayMinRepayNum;//每天最小还款笔数
    public static int quickDayMaxRepayNum;//每天最大还款笔数
    public static int quickSingleMinMoney;//单笔最小交易金额
    public static int quickSingleMaxMoney;//单笔最大交易金额
    private static int splitMinute;//计划间隔分钟数

    static {
        PlanConfig quickConfig = WebUtil.getPlanConfigByType(Constants.REPAY_TYPE_QUICK);
        if (null == quickConfig) {
            quickRepayMinAmount = 1000;
            quickRepayMaxAmount = 50000;
            quickDayMinRepayNum = 3;
            quickDayMaxRepayNum = 8;
            quickSingleMinMoney = 100;
            quickSingleMaxMoney = 2000;
            splitMinute = 30;
        } else {
            quickRepayMinAmount = quickConfig.getRepayMinAmount().intValue();
            quickRepayMaxAmount = quickConfig.getRepayMaxAmount().intValue();
            quickDayMinRepayNum = quickConfig.getDayMinNum();
            quickDayMaxRepayNum = quickConfig.getDayMaxNum();
            quickSingleMinMoney = quickConfig.getSingleMinAmount().intValue();
            quickSingleMaxMoney = quickConfig.getSingleMaxAmount().intValue();
            splitMinute = quickConfig.getSplitMinute();
        }
    }

    /**
     * 获取还款天数和每天还款次数
     *
     * @param repayAmount 还款金额
     * @param repayDays   还款天数（账单日和还款日以及当前日期计算出来的最多还款天数）
     * @return
     */
    public static Map<String, Integer> calDayRepayNumInfo(int repayAmount, int repayDays, Integer oldMinRepayNum) {

        int dayRepayAmount = (int) Math.ceil((double) (repayAmount / repayDays));
        int dayMaxRepayAmount = quickDayMaxRepayNum * quickSingleMaxMoney;
        int dayMinRepayAmount = quickDayMinRepayNum * quickSingleMinMoney;

        //先从每天最少还款开始
        BigDecimal nearPer = new BigDecimal(dayMinRepayAmount).divide(new BigDecimal(dayRepayAmount), 2, BigDecimal.ROUND_UP);
        while (nearPer.compareTo(new BigDecimal("0.5")) >= 0) {
            repayDays = repayDays - 1;
            dayRepayAmount = (int) Math.ceil((double) (repayAmount / repayDays));
            nearPer = new BigDecimal(dayMinRepayAmount).divide(new BigDecimal(dayRepayAmount), 2, BigDecimal.ROUND_UP);
        }

        int dayRepayNum = RandomUtil.getRandomInt(null == oldMinRepayNum ? quickDayMinRepayNum : oldMinRepayNum, quickDayMaxRepayNum);
        dayMaxRepayAmount = dayRepayNum * quickSingleMaxMoney;
        dayMinRepayAmount = dayRepayNum * quickSingleMinMoney;

        nearPer = new BigDecimal(dayMinRepayAmount).divide(new BigDecimal(dayRepayAmount), 2, BigDecimal.ROUND_UP);
        while (nearPer.compareTo(new BigDecimal("0.8")) >= 0) {
            dayRepayNum = dayRepayNum - 1;
            dayMinRepayAmount = dayRepayNum * quickSingleMinMoney;
            nearPer = new BigDecimal(dayMinRepayAmount).divide(new BigDecimal(dayRepayAmount), 2, BigDecimal.ROUND_UP);
        }

        nearPer = new BigDecimal(dayRepayAmount).divide(new BigDecimal(dayMaxRepayAmount), 2, BigDecimal.ROUND_UP);
        while (nearPer.compareTo(new BigDecimal("0.8")) >= 0) {
            dayRepayNum = dayRepayNum + 1;
            dayMaxRepayAmount = dayRepayNum * quickSingleMaxMoney;
            nearPer = new BigDecimal(dayRepayAmount).divide(new BigDecimal(dayMaxRepayAmount), 2, BigDecimal.ROUND_UP);
        }

        Map<String, Integer> resMap = new HashMap<>();
        resMap.put("repayDays", repayDays);
        resMap.put("dayRepayNum", dayRepayNum);
        resMap.put("repayNum", repayDays * dayRepayNum);
        return resMap;
    }

    /**
     * 计算总交易金额
     * 总交易金额=目标金额+（计划期数*1+ 目标金额*手续费率+(计划期数*1+ 目标金额*手续费率)*手续费率）
     * 在判断金额是否为整数，如果是则+1，如果为否向上取整；
     * <p>
     * 因为手续费也会产生手续费，所以要加上手续费的手续费
     *
     * @param repayAmount  计划还款总金额(元)
     * @param repayNum     还款次数
     * @param merFeeRate   商户交易手续费率
     * @param merSingleFee 商户单笔代付成本
     * @return
     */
    public static int calTotalPayAmount(int repayAmount, int repayNum, double merFeeRate, double merSingleFee) {
        Double fee = repayAmount * merFeeRate + repayNum * merSingleFee;
        Double totalFee = fee + fee * merFeeRate;
        int totalPayAmount = repayAmount + (int) Math.floor(totalFee + 1);
        log.info("总消费金额totalPayAmount=" + totalPayAmount);
        return totalPayAmount;
    }

    /**
     * 计算首笔交易金额
     *
     * @param repayAmount      还款金额
     * @param totalRepaayNum   总还款期数
     * @param repayDays        还款天数
     * @param dayRepayNum      每天还款次数
     * @return
     */
    public static int calFirstPayAmount(int repayAmount, int totalRepaayNum, int repayDays, int dayRepayNum) {

        int firstPayMinAmount = (int) Math.ceil((double) (repayAmount / totalRepaayNum));
        int maxMult = repayDays >= dayRepayNum ? repayDays : dayRepayNum;
        double dayAmountPercent = (double) (firstPayMinAmount * maxMult) / (double) (repayAmount);
        int firstPayAmount = firstPayMinAmount + (int) Math.ceil(firstPayMinAmount * dayAmountPercent);
        return firstPayAmount;
    }

    /**
     * 计算单笔最低消费金额
     *
     * @param repayTotalAmount 消费的总金额
     * @param firstPayAmount   第一笔交易金额
     * @param repayNum         还款总期数
     * @return
     */
    public static int calSingleMinAmount(int repayTotalAmount, int firstPayAmount, int repayNum) {
        int calSingleMinAmount = repayTotalAmount / repayNum - (firstPayAmount - repayTotalAmount / repayNum);
        int retSingleMinAmount = calSingleMinAmount;
        if (calSingleMinAmount >= firstPayAmount || calSingleMinAmount <= quickSingleMinMoney) {
            retSingleMinAmount = quickSingleMinMoney;
        }
        return retSingleMinAmount;
    }

    /**
     * 智能还款生成计划算法
     *
     * @param repayTotalAmount
     * @param firstPayAmount
     * @param repayDays
     * @param dayRepayNum
     * @param repayBeginTime
     * @param repayBeginHour
     * @param repayEndHour
     * @param repayNum
     * @param repayFeeRate
     * @param repaySingleFee
     * @return
     * @throws Exception
     */
    public static List<Map<String, String>> repayPlanTask(int repayTotalAmount, int firstPayAmount, int repayDays, int dayRepayNum, Date repayBeginTime, int repayBeginHour, int repayEndHour, int repayNum, BigDecimal repayFeeRate, BigDecimal repaySingleFee) throws Exception {
        int calSingleMinAmount = calSingleMinAmount(repayTotalAmount, firstPayAmount, repayNum);
        StringBuffer sb = new StringBuffer("快捷还款计划建立计划,");
        sb.append("计划消费总金额：").append(repayTotalAmount).append("；计划消费最大金额：").append(firstPayAmount)
                .append("；计划还款总天数：").append(repayDays).append("；计划还款开始时间：")
                .append(DateUtil.formatDateTime(repayBeginTime.getTime())).append("；每天还款允许时间段-开始时间：").append(repayBeginHour)
                .append("；每天还款允许时间段-结束时间：").append(repayEndHour).append("；每天还款次数：").append(dayRepayNum)
                .append(";还款次数:").append(repayNum).append(";单笔消费最小金额：").append(calSingleMinAmount)
                .append("；交易手续费率：").append(String.valueOf(repayFeeRate)).append(";单笔代付手续费：").append(String.valueOf(repaySingleFee));
        log.info(sb.toString());

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        List<Map<String, String>> repaymentPlanList = new ArrayList<>();

        //根据总还款金额和还款次数拆分金额
        List<Integer> repaymentAmountList = splitRedPackets(repayTotalAmount, repayNum, "amount", firstPayAmount, calSingleMinAmount);
        if (CollectionUtils.isEmpty(repaymentAmountList)) {
            repaymentAmountList = splitRedPackets(repayTotalAmount, repayNum, "amount", firstPayAmount, calSingleMinAmount);
        }
        System.out.println("拆分消费金额：repaymentAmountList=" + repaymentAmountList);
        //根据还款次数和还款天数计算每天还的笔数
        List<Integer> repaymentDayList = new ArrayList<>();
        int remainRepaymentNum = repayNum;
        while (remainRepaymentNum > 0) {
            repaymentDayList.add(remainRepaymentNum > dayRepayNum ? dayRepayNum : remainRepaymentNum);
            remainRepaymentNum = remainRepaymentNum - dayRepayNum;
        }
        System.out.println("每天还款次数：repaymentDayList=" + repaymentDayList);
        if (CollectionUtils.isEmpty(repaymentDayList) || CollectionUtils.isEmpty(repaymentAmountList)) {
            return null;
        }

        int planIndex = 1;
        int repaymentAmountListIndex = 0;
        Date now = new Date();

        for (int i = 0; i < repaymentDayList.size(); i++) {//遍历总天数
            int dayRepaymentNum = repaymentDayList.get(i);//每天应还款次数
            String repaymentPlanTimeStr = sdf.format(repayBeginTime);
            //当前时间是计划开始时间，则从明天开始
            if (DateUtil.daysBetween(now, repayBeginTime) == 0) {
                repayBeginTime = DateUtil.addDays(repayBeginTime, 1);
                repaymentPlanTimeStr = sdf.format(repayBeginTime.getTime());
            }
            int hoursNum = dayRepaymentNum * 2 + 1;//今天时间点个数
            if ((i + 1) % 2 == 0) {
                hoursNum = hoursNum + 1;
            }
            List<String> hoursList = getSortTime(hoursNum, repayBeginHour, repayEndHour, 1);
            System.out.println("hoursList=" + hoursList);
            if (hoursList == null || hoursList.isEmpty()) {
                return null;
            }

            int hoursIndex = 0;
            //遍历每天的还款次数
            for (int j = 0; j < dayRepaymentNum; j++) {
                int repaymentAmount = repaymentAmountList.get(repaymentAmountListIndex);
                BigDecimal repayAmountBig = new BigDecimal(String.valueOf(repaymentAmount));//本次消费金额
                if (repayAmountBig.compareTo(new BigDecimal(String.valueOf(firstPayAmount))) == 1) {
                    System.out.println("消费金额大于单笔最大消费金额");
                    return null;
                }
                BigDecimal repayCashAmountBig = repayAmountBig.subtract(repaySingleFee).subtract(repayAmountBig.multiply(repayFeeRate)).setScale(2, BigDecimal.ROUND_DOWN);
                System.out.println("还款金额：" + repayCashAmountBig + "，消费金额：" + repayAmountBig);
                //先消费再代付，无保证金模式
                //消费
                Map<String, String> outPlanMap = new HashMap<>();
                outPlanMap.put("planTime", repaymentPlanTimeStr + " " + hoursList.get(hoursIndex));
                outPlanMap.put("planType", "OUT");
                outPlanMap.put("planAmount", String.valueOf(new BigDecimal(String.valueOf(repayAmountBig))));
                outPlanMap.put("planIndex", String.valueOf(planIndex));
                outPlanMap.put("planFee", String.valueOf(repayAmountBig.subtract(repayCashAmountBig)));
                repaymentPlanList.add(outPlanMap);

                hoursIndex++;
                planIndex++;

                //还款
                String repaymentPlanTime = repaymentPlanTimeStr + " " + hoursList.get(hoursIndex);//具体操作时间
                Map<String, String> repaymentPlanMap = new HashMap<>();
                repaymentPlanMap.put("planTime", repaymentPlanTime);
                repaymentPlanMap.put("planType", "IN");
                repaymentPlanMap.put("planAmount", String.valueOf(repayCashAmountBig));
                repaymentPlanMap.put("planFee", String.valueOf(repaySingleFee));
                repaymentPlanMap.put("planIndex", String.valueOf(planIndex));
                repaymentPlanList.add(repaymentPlanMap);
                planIndex++;
                repaymentAmountListIndex++;
                hoursIndex++;
            }
            repayBeginTime = DateUtil.addDays(repayBeginTime, 1);
        }
        //将首两笔的计划时间改为今天
        for (int k = 0; k < repaymentPlanList.size(); k++) {
            if (k == 0) {
                repaymentPlanList.get(0).put("planTime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            } else if (k == 1) {
                Calendar nowCal = Calendar.getInstance();
                nowCal.setTime(new Date());
                nowCal.add(Calendar.MINUTE, splitMinute);
                String secondTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(nowCal.getTime());
                repaymentPlanList.get(1).put("planTime", secondTime);
            }
        }
        log.info("还款计划明细：repaymentPlanList=" + repaymentPlanList);
        return repaymentPlanList;
    }

    /**
     * 拆分金额
     *
     * @param money ：总金额
     * @param count ：个数
     * @return
     */
    public static List<Integer> splitRedPackets(int money, int count, String calType, int max, int min) {
        int originalMoney = money;
        int originalCount = count;
        //合法性校验
        if (!isRight(money, count, min, max)) {
            return null;
        }
        //金额列表
        List<Integer> list = new ArrayList<>();
        try {
            if ("amount".equals(calType)) {
                //分配金额
                for (int i = 0; i < count; i++) {
                    int one = randomRedPacket(money, min, max, count - i, max, min);
                    if (i == 0) {
                        one = max;
                    }
                    list.add(one);
                    money -= one;
                    System.out.println((i + 1) + "---" + one + "----" + money);
                    //合法性校验
                    if (count - i - 1 > 0 && !isRight(money, count - i - 1, min, max)) {
                        return null;
                    }
                    if (one == -1) {
                        list = splitRedPackets(originalMoney, originalCount, "amount", max, min);
                    }
                }
            } else if ("day".equals(calType)) {
                int tmp = money / count + 1;
                if (quickDayMaxRepayNum < tmp) {
                    max = tmp;
                }
                //分配金额
                for (int i = 0; i < count; i++) {
                    int one = randomRedPacket(money, min, max, count - i, max, min);
                    list.add(one);
                    money -= one;
                    if (one == -1) {
                        list = splitRedPackets(originalMoney, originalCount, "day", max, min);
                    }
                }
            }
        } catch (StackOverflowError e) {
            return null;
        }
        return list;
    }

    /**
     * 随机分配一个金额
     * 允许小数，则金额单位为分
     *
     * @param money
     * @param minS  :最小金额
     * @param maxS  ：最大金额
     * @param count
     * @return
     */
    private static int randomRedPacket(int money, int minS, int maxS, int count, int maxMoney, int minMoney) {
        if (count == 1 && money > maxMoney) {
            return -1;//重新计算
        }
        //若是只有一个，直接返回金额
        if (count == 1) {
            return money;
        }
        //若是最小金额 == 最大金额， 直接返回最小金额
        if (minS == maxS) {
            return minS;
        }
        int tmp = Integer.parseInt(String.valueOf(new BigDecimal(money).divide(new BigDecimal(count), 0, BigDecimal.ROUND_UP)));
        if (tmp == maxMoney) {
            return maxMoney;
        }
        //校验 最大值 max 要是比money 金额高的话？ 去 money 金额
        int max = maxS > money ? money : maxS;
        //随机一个金额 = 随机一个数* (金额-最小)+最小
        int one = ((int) Math.rint(Math.random() * (max - minS) + minS));
        //剩下的金额
        int moneyOther = money - one;
        //System.out.println("moneyOther="+moneyOther+",count="+count);
        //校验这种随机方案是否可行，不合法的话，就要重新分配方案
        if (isRight(moneyOther, count - 1, minS, maxS)) {
            return one;
        } else {
            //重新分配
            double avg = moneyOther / (count - 1);
            //本次金额过大，导致下次的金额过小；如果金额过大，下次就随机一个小值到本次金额的一个金额
            if (avg < minMoney) {
                //递归调用，修改最大金额
                return randomRedPacket(money, minS, one, count, maxMoney, minMoney);

            } else if (avg > maxMoney) {
                //递归调用，修改最小金额
                return randomRedPacket(money, one, maxS, count, maxMoney, minMoney);
            }
        }
        return one;
    }

    /**
     * 合法性校验
     *
     * @param money
     * @param count
     * @return
     */
    private static boolean isRight(int money, int count, int min, int max) {
        double avg = money / count;
        //小于最小金额
        if (avg < min) {
            return false;
            //大于最大金额
        } else if (avg > max) {
            return false;
        }
        return true;
    }

    /**
     * 取范围内随机数
     *
     * @param min
     * @param max
     * @return
     */
    public static int getRandom(int min, int max) {
        if (max == 0) {
            return 0;
        }
        Random random = new Random();
        int s = random.nextInt(max) % (max - min + 1) + min;
        return s;

    }

    /**
     * @param num
     * @param min
     * @param max
     * @return
     */
    public static List<Integer> getRandomAndSort(int num, int min, int max) {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < num; i++) {
            list.add(getRandom(min, max));
        }
        Collections.sort(list);
        return list;
    }

    /**
     * 生成时间段内一定数量的时间，并按时间排序
     *
     * @param num
     * @param min
     * @param max
     * @return
     */
    public static List<String> getSortTime(int num, int min, int max, int operNum) throws Exception {
        String last = max + ":00:00";
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        List<String> list = new ArrayList<>();
        List<Calendar> randomTimes = new ArrayList<>();
        for (int i = 0; i < num; i++) {
            String tmp = getRandom(min, max) + ":" + getRandom(0, 60) + ":" + getRandom(0, 60);
            Calendar tmpTime = Calendar.getInstance();
            tmpTime.setTime(sdf.parse(tmp));
            randomTimes.add(i, tmpTime);
        }
        //排序
        for (int i = 1; i < randomTimes.size(); i++) {
            for (int j = 0; j < randomTimes.size() - i; j++) {
                if (randomTimes.get(j).after(randomTimes.get(j + 1))) {
                    Calendar tmpCal = Calendar.getInstance();
                    tmpCal.setTime(randomTimes.get(j).getTime());
                    randomTimes.set(j, randomTimes.get(j + 1));
                    randomTimes.set(j + 1, tmpCal);
                }
            }
        }
        //时间间隔重排
        for (int i = 0; i < randomTimes.size(); i++) {
            Calendar tmpCal = Calendar.getInstance();
            tmpCal.setTime(randomTimes.get(i).getTime());
            if (i != randomTimes.size() - 1) {
                Calendar nextLastTimeCal = Calendar.getInstance();
                nextLastTimeCal.setTime(sdf.parse(last));
                nextLastTimeCal.add(Calendar.MINUTE, -splitMinute * (randomTimes.size() - i - 1));
                Calendar tmpNextCal = randomTimes.get(i + 1);
                System.out.println((i + 1) + "=" + sdf.format(tmpNextCal.getTime()) + "--" + tmpNextCal.getTimeInMillis() + "," + i + "=" + sdf.format(tmpCal.getTime()) + "--" + tmpCal.getTimeInMillis() + ",nextLastTimeCal=" + sdf.format(nextLastTimeCal.getTime()));
                if (tmpNextCal.getTimeInMillis() - tmpCal.getTimeInMillis() < splitMinute * 60 * 1000 || tmpNextCal.after(nextLastTimeCal)) {
                    tmpCal.add(Calendar.MINUTE, splitMinute);
                    randomTimes.set(i + 1, tmpCal);
                }
            } else {
                Calendar lastTime = Calendar.getInstance();
                lastTime.setTime(sdf.parse(last));
                if (tmpCal.after(lastTime)) {
                    if (operNum < 10) {
                        return getSortTime(num, min, max, operNum + 1);
                    } else {
                        return null;
                    }
                }
            }
        }
        for (int i = 0; i < randomTimes.size(); i++) {
            list.add(sdf.format(randomTimes.get(i).getTime()));
        }
        return list;
    }

}
